Процедуры в Maple(продолжение)

 Опции процедуры

        В процедуре в ее необязательном разделе опций, начинающемся со слов option или options, могут быть объявлены опции. Они определяют различные дополнительные характеристики процедуры и задаются именами name или уравнениями вида name = значение, где name может быть одним из следующих ключевых слов: copyright, trace, inline, operator, arrow, system, builtin, call_externall, remember, cache, overload. Некоторые опции можно задавать без ключевого слова одним значением.

Уведомление о правах

        Опция copyright = text значением text предоставляет пользователю общую информацию о процедуре и правах разработчиков на нее. Значение text может быть задано строкой, литералом в обратных кавычках или литералом без пробелов. Например, option copyright = `Copyright (C) Tula, TGPU, chair Computer Science, Inc.2006`. При наличии опции copyright по eval и print выводится структурированный листинг процедуры с пропуском комментариев по одному предложению на строку, но без нумерации строк. Напомним, что по команде showstat подобный листинг выводится без разделов option и description, но с нумерацией строк.

        Опция `Copyright …`. Если опцию copyright задать без ключевого слова одним значением в обратных кавычках, но начинающимся со слова Copyrigth, то по умолчанию по eval и print вместо процедуры будет выводиться ее шаблон. При установке interface(verboseproc = n) (n ³ 2) выводится процедура.

Трассирование выполнения процедуры

        Опция trace. Наличие опции trace приводит к возвращению не только результатов вычислений по процедуре, но и к выводу некоторой дополнительной информации о ходе ее выполнения. Например, при каждом обращении к процедуре выводятся тексты ее внутренних процедур, а также последовательные значения, принимаемые переменными и выражениями. Опцию trace  можно использовать при отладке процедур.

        Пример 1. В процедуру triangle после раздела description вставим строку option trace. Тогда при обращении к triangle, например, в виде triangle([[1, 2], [5, 1], [6, 0]]), получим следующий вывод:

> 

triangle := proc(li::listlist(integer))

 

 

   local s, t, r, dis, lineQ;

 

 

   description "Perimeter of a triangle";

 

 

   #

 

 

   dis := proc(A, B)   # расстояние между A и B

 

 

      evalf(sqrt((A[1]-B[1])^2+(A[2]-B[2])^2))

 

 

   end proc;

 

 

   #

 

 

   lineQ := proc(li)  # вырожден ли треугольник?

 

 

     Matrix([[op(li[1]), 1], [op(li[2]), 1], [op(li[3]), 1]]);

 

 

     LinearAlgebra[Determinant](%);

 

 

     evalb(% <> 0)

 

 

  end proc:

 

 

   #

 

 

   if  nops(li) = 3 and nops(li[1]) = 2 then

 

 

      if  lineQ(li) then

 

 

         s := li[1]: t := li[2]: r := li[3];

 

 

         dis(s, t)+dis(s, r)+dis(t, r);

 

 

      else

 

 

         print("That is a singular triangle")

 

 

      end if

 

 

   else

 

 

      print("That are not 3 points")

 

 

   end if

 

 

end proc:

 

{--> enter triangle, args = [[1, 2], [5, 1], [6, 0]]

dis := proc (A, B) evalf(sqrt((A[1]-B[1])^2+(A[2]-B[2])^2)) end proc;

lineQ := proc(li)

   Matrix([[op(li[1]), 1], [op(li[2]), 1], [op(li[3]), 1]]);

   LinearAlgebra[Determinant](%);

   evalb(% <> 0);

end proc:

s := [1, 2]

t := [5, 1]

r := [6, 0]

10.92248400

<-- exit triangle (now at top level) = 10.92248400}

10.92248400

    Здесь представлена следующая информация: значения аргументов при обращении к процедуре triangle (--> enter triangle, args =); текст внутренних процедур dis и lineQ; последовательные значений, принимаемых переменными и выражениями triangle и результаты вычислений по triangle (<-- exit triangle).

Объявление подставляемой процедуры

        Опция inline. Если процедура содержит опцию inline, то она объявляется inline-объектом или, по-другому, подставляемым объектом. Это означает, что обращения к такой процедуре, в том числе и из других процедур и модулей, до вычислений на этапе трансляции замещаются ее телом с конкретными наборами фактических значений. Опция inline применяется в часто используемых процедурах с целью оптимизации вычислений по времени. Однако делать это можно далеко не всегда. Основные ограничения здесь таковы:

·       тело процедуры должно состоять из одного выражения или последовательности выражений, но не может быть предложением (командой) или последовательностью предложений языка Maple;

·       в процедуре не должно быть локальных объектов;

·       процедура не может быть рекурсивной;

·       обращения к inline-процедуре должны быть с количеством фактических значений, равным количеству ее формальных параметров.

        Примеры 2. Здесь в первом примере процедура ff благодаря опции inline объявляется подставляемым объектом. В теле процедуры gg имеются два обращения к ff. Однако при конкретных вычислениях по gg эти обращения не осуществляются. Уже на этапе трансляции организуются подстановки тела ff в gg с требуемым набором фактических значений.

        Во втором примере в процедуре hh вычисления, предусмотренные в gg, реализуются 106 раз, и подсчитывается израсходованное на это время в секундах. При этом используется та же самая процедура ff сначала с опцией inline, а затем без нее. Сравнение времени вычисления в том и другом случае свидетельствует о несомненной пользе применения inline-процедур.  

> 

restart:

{¿}

 

ff := proc(x, a, b) option inline; sin(a*x+b) end proc;

{¿}

 

ff := proc (x, a, b) option inline; sin(a*x+b) end proc

 

 

 

 

> 

gg := proc(x, a, b) a*ff(x, a, b+1)+b*ff(x, a+1, b) end proc;

{¿}

 

gg := proc (x, a, b) a*sin(a*x+b+1)+b*sin((a+1)*x+b) end proc

 

 

hh := proc(x, a, b)

{Shift+¿}

 

   local tim, j;

{Shift+¿}

 

   tim := time():

{Shift+¿}

 

   for j from 1 to 10^6 do

{Shift+¿}

 

     a*ff(x, a, b+1)+b*ff(x, a+1, b)

{Shift+¿}

 

   end do;

{Shift+¿}

 

   tim := time()-tim;

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

# ff выполнена с опцией inline, hh выполнена; 

{Shift+¿}

 

hh(1, 1, 1);

{¿}

> 

# ff выполнена без опции inline, hh выполнена; 

{Shift+¿}

 

hh(1, 1, 1);

{¿}

Вывод процедуры в функциональной форме

Опция operator. Если тело body состоит из одного предложения, то опция operator предписывает выводить процедуру в функциональной форме записи. Например,

 

> 

g := proc(x) option operator; x+sin(x) end proc;

{¿}

 

g := x -> x+sin(x) end proc

Блокирование упрощений

        Опция arrow. Наличие опции предписывает отменить некоторые правила упрощения, применяемые при вычислении процедуры к глобальным именам.

Объявление системной процедуры

Опция system используется системой и служит для придания процедурам Maple-библиотеки статуса “системная”. При наличии опций remember и system при сборках мусора это обеспечивает автоматическое удаление remember-таблицы. При отсутствии опции system сборки мусора не затрагивают запоминающей таблицы.

Объявление встроенной процедуры

Опция builtin = n назначает процедуре статус “встроенная”, записывается первой среди других опций и используется системой. Для встроенных процедур pro вывод по командам eval и print ограничивается шаблоном вместе с опцией builtin и уникальным идентификационным номером процедуры. Например,

> 

eval(length);

{¿}

 

proc () option builtin 224 end proc

Задание опции  builtin = n для пользовательской процедуры не приводит к ошибке, но и не придает ей статус (тип) builtin.

Объявление внешней процедуры

Опция call_externall = name объявляет об использовании в текущей процедуре внешней программы name, написанной на языке C, Fortran или Java. Перед этим должна быть сформирована процедура define_external, связывающая исходную и внешнюю процедуры. Но это другая тема и на ней мы останавливаться не будем.

Запоминающие таблицы процедуры

        Ниже описываются опции remember и cache, которые инициируют формирование таблиц фактических параметров процедуры и соответствующих им результатов вычислений, и рассказывается о некоторых простейших средствах управления создаваемыми таблицами. Подобные динамические объекты называются соответственно запоминающими таблицами и кэш-таблицами (remember – помнить, хранить в памяти; cache – быстродействующая буферная память).

Привязка к процедуре стандартной таблицы ее значений

        Опция remember при каждом обращении к процедуре инициирует запись фактических значений и получаемых по ним результатов вычислений в специальную запоминающую таблицу (remember-таблицу). Эта таблица пополняется и используется так. При вызове процедуры с конкретным набором фактических значений сначала по запоминающей таблице проверяется, не проводились ли эти вычисления ранее. Если это так, то требуемый результат просто извлекается из таблицы. Если нет – то проводятся вычисления и фактические значения вместе с полученным результатом заносятся в таблицу. Таким образом, в запоминающей таблице сохранятся всю история вызовов процедуры или, по-другому, трасса вычислений по ней.

        Запоминающая таблица, если она есть, доступна пользователю как четвертый компонент процедуры pro по команде op(4, eval(pro)). Для процедур с переменными объектами, такими, например, как rtable, запоминающие таблицы использовать нельзя. Особенно полезен и эффективен механизм запоминающих таблиц при работе с рекурсивными процедурами. При установке interface(verboseproc = 3) команды eval(pro) и print(pro) выводят не только текст процедуры pro, но и ее запоминающую таблицу.

        Команда forget(pro) удаляет (чистит) запоминающую таблицу процедуры pro.  

        Команда forget(pro, a, b, …) удаляет (чистит) элемент запоминающей таблицы, соответствующий набору фактических значений a, b, … процедуры pro.

        Команда pro(a, b, …) := expr модифицирует имеющийся или создает новый элемент (вход) запоминающей таблицы из набора фактических значений a, b, … процедуры pro и соответствующего им значения выражения expr. 

        Запоминающие таблицы с успехом можно применять для построения процедур вычисления выражений, имеющих заранее известные особенности. Пусть в процедуре использована опция remember и командой pro(a, b, …) := expr в ее запоминающую таблицу внесены все входы с особенными значениями выражения. Тогда вычисления по процедуре не будут приводить к сбоям потому, что они всегда начинаются с просмотра запоминающей таблицы. И если в ней для заданного набора входных параметров уже имеется значение, то оно и будет возвращено, минуя процедуру.

        Пример 3. Здесь демонстрируется работа с запоминающей таблицей на примере рекурсивной процедуры вычисления n-го числа Фибоначчи: fib(0) = fib(1) = 1; fib(n) = fib(n-1)+fib(n-1) (при n > 1).

 

> 

restart:

{¿}

> 

fib := proc(n) option remember;

{Shift+¿}

 

     fib(n-1)+fib(n-2);

{Shift+¿}

 

end proc: 

{Shift+¿}

 

fib(0) := 1: fib(1) := 1:

{¿}

> 

fib(5);

{Shift+¿}

 

op(4, eval(fib));

{¿}

> 

forget(fib, 3):

{Shift+¿}

 

op(4, eval(fib));

{¿}

> 

fib(9) := b:

{Shift+¿}

 

op(4, eval(fib));

{¿}

> 

interface(verboseproc = 3);

{Shift+¿}

 

eval(fib);

{¿}

 

proc(n)                               # вывод процедуры

 

 

option remember;

 

 

   fib(n-1)+fib(n-2)

 

 

end proc

 

 

#(0) = 1        # вывод запоминающей таблицы

 

 

#(1) = 1

 

 

#(2) = 2

 

 

#(5) = 8

 

 

#(4) = 5

 

 

#(9) = b

Привязка к процедуре хэш-таблицы ее значений

       Опция cache при обращениях к процедуре инициирует запись фактических значений и получаемых по ним результатов вычислений в специальную хэш-таблицу. Такие таблицы аналогичны  запоминающим таблицам, но имеют несколько отличный от них механизм управления. Опция cache несовместима с опциями remember и system. Использование в процедурах хэш-таблиц вместо запоминающих таблиц, как правило, несколько улучшает их временные характеристики. Кроме того, для работы с хэш-таблицами, наряду с процедурой forget, можно использовать и специальные средства, сосредоточенные в пакете Cache.

        Пример 4. Здесь демонстрируется работа с хэш-таблицей на примере рекурсивных процедур вычисления n-го числа последовательности: a(0) = a(1) = 1; a(n) = 2*a(n-1)+3*a(n-2) (при n > 1). Процедура ttt с опцией cache и процедура sss с опцией remember реализуют вычисление n-го члена этой последовательности. Вычисление элемента a(15000) по ttt и sss прошло соответственно за время 1.421 и 1.677 секунды. 

 

> 

restart:

{¿}

> 

ttt := proc(n) option cache;

{Shift+¿}

 

     if n < 2 then 1 else 2*ttt(n-1)+3*ttt(n-2) end if

{Shift+¿}

 

end proc: 

{¿}

> 

ti := time(): ttt(15000): time()-ti;           # время с cache

{¿}

> 

sss := proc(n) option remember;

{Shift+¿}

 

     if n < 2 then 1 else 2*sss(n-1)+3*sss(n-2) end if

{Shift+¿}

 

end proc: 

{¿}

> 

ti := time(): sss(15000): time()-ti;   # время с remember

{¿}

> 

forget(ttt): ttt(4); op(4, eval(ttt));

{¿}

Перегружаемые вычисления

        Перегрузкой называют вычисления, при которых транслятор выбирает одну подходящую из нескольких существующих реализаций конкретной процедуры при обращениях к ней с разными типами фактических значений.        Опция overload и команда overload(…) позволяют организовать перегружаемые вычисления.

Объявление перегружаемой процедуры

        Опция overload объявляет процедуру pro, в которой она находится, перегружаемой. Для организации перегружаемых вычислений, прежде всего, необходимо из pro и иных процедур решения исходной задачи командой opro:= overload(li) создать новую перегружаемую процедуру-контейнер opro. Включаемые в opro процедуры задаются без имен в виде элементов списка. Далее, конкретное обращение к opro с набором фактических значений будет приводить к проверкам допустимости их типов для последовательных встроенных процедур opro. Первая из процедур, удовлетворяющая этому условию, и используется для вычислений.

        Команда opro:= overload(li) по списку имен li перегружаемых процедур создает новую перегружаемую процедуру с именем opro, тело которой получается из li заменой всех элементов списка на соответствующие процедуры. Среди элементов li могут быть имена перегружаемых процедур-контейнеров, ранее полученных командой opro:= overload(li). Отметим, что перегрузка может быть реализована в модулях.

        Пример 5. Создать процедуру, по которой проводятся следующие перегружаемые вычисления:

1.    по списку li и выражению expr возвращается список, получаемый добавлением expr в конец li;

2.    по множеству se и выражению expr возвращается множество, получаемое добавлением expr к se;

3.    по вектору-столбцу ve типа Vector и выражению expr возвращается вектор-строка типа Vector, получаемая добавлением expr в конец ve и транспонированием результата.

        Решение. Приведенное ниже решение задачи разбито на три этапа. Сначала создаются перегружаемые процедуры alist, aset и aVector решения отдельных задач 1-3. Затем по списку [alist, aset] с помощью команды overload создается перегружаемая процедура appe для решения задач 1 и 2. И, наконец, по списку [appe, pVector] с помощью команды overload создается перегружаемая процедура append для решения задач 1-3. Заметим, что ничто не мешало нам создать append и по списку [alist, aset, aVector].

> 

restart:                                                               # этап 1

{¿}

> 

alist := proc(li::list, e::anything)                    # задача 1

{Shift+¿}

 

   option overload; [op(li), e];

{Shift+¿}

 

end proc:

{¿}

> 

alist([1, 6], 7);

{¿}

> 

aset := proc(se::set, e::anything)                   # задача 2

{Shift+¿}

 

  option overload; {op(se), e};

{Shift+¿}

 

end proc:

{¿}

> 

aset({3, 6, 8, 9}, 7);

{¿}

> 

aVector := proc(ve::Vector, e::anything)       # задача 3

{Shift+¿}

 

local t, B;

{Shift+¿}

 

option overload;

{Shift+¿}

 

   t := op(1, ve);

{Shift+¿}

 

   B := Vector(t+1, op(2, ve)): B[t+1] := e;

{Shift+¿}

 

   LinearAlgebra[Transpose](B);

{Shift+¿}

 

end proc:

{¿}

> 

aVector(<1, 2, 3>, qwe);

{¿}

> 

appe := overload([aset, alist]);                          # этап 2

{¿}

 

appe := proc()

 

 

   option overload;

 

 

   [proc( li::list, e::anything) option overload;

 

 

      [op(li), e] end proc;,

 

 

    proc( se::set, e::anything) option overload;

 

 

      {op(se), e} end proc;]

 

 

end proc;

 

> 

appe('[a, 6, 7]', 'ttt');

{¿}

> 

appe({1, 8, 9, 7}, 77);

{¿}

> 

append := overload([appe, aVector]):              # этап 3

{¿}

> 

append('[a, 6, 7]', 'ttt'),

{Shift+¿}

 

append({1, 8, 9, 7}, 77),

{Shift+¿}

 

append(<6, 9>, 'e');

{¿}

        Пример 6. Написать модуль, по которому реализуются следующие перегружаемые вычисления:

1.    по списку li и выражению expr возвращается список, получаемый добавлением expr в начало li;

2.    по множеству se и выражению expr возвращается множество, получаемое добавлением expr к se.

        Решение. К этому примеру можно будет вернуться позже, после обстоятельного разговора о модулях. Сейчас же дадим краткое пояснение к приведенному решению. В модуле PrependListSet объявлены три экспортируемых члена – перегружаемые процедуры prepend, plist и pset. После определения plist и pset командой overload по списку [plist, pset] создается процедура prepend. Поэтому по prepend можно решать и задачу 1, и задачу 2. Кроме того, по plist можно решать задачу 1, а по pset задачу 2.

> 

restart:                                                              

{¿}

> 

PrependListSet := module() option package;

{Shift+¿}

 

   export prepend, plist, pset;

{Shift+¿}

 

   plist := proc(li::list, e::anything)

{Shift+¿}

 

      option overload; [e, op(li)];

{Shift+¿}

 

   end proc:  

{Shift+¿}

 

   pset := proc(se::set, e::anything)

{Shift+¿}

 

      option overload; {op(se), e};

{Shift+¿}

 

   end proc:

{Shift+¿}

 

   prepend := overload([plist, pset]);

{Shift+¿}

 

end module:

{¿}

> 

prepend('[a, 6, 7]', 'ttt'), prepend({1, 8, 9, 7}, 77);

{¿}

 

prepend([a, 6, 7], ttt), prepend({1, 7, 8, 9}, 77)

 

> 

with(PrependListSet);

{¿}

> 

prepend('[a, 6, 7]', 'ttt'), prepend({1, 8, 9, 7}, 77);

{¿}

> 

plist('[a, 6, 7]', 'ttt'), pset({1, 8, 9, 7}, 77);

{¿}

> 

unwith(PrependListSet);

{¿}      

        Остановимся еще на одном моменте. Перегружаемые вычисления можно использовать не только для решения конкретной задачи при различных типах ее фактических значений, но и для решения серии различных задач. Причем, вопрос о том, какая же из задач должна решаться при обращении к перегружаемой процедуре, можно связать с типом получаемых фактических значений. Рассмотрим конкретный пример.

        Пример 7. Пусть n – целое число. Создать процедуру facto, по которой реализуются следующие перегружаемые вычисления.

1.    при обращении к facto с аргументом n возвращается каноническое разложение n на простые множители (96 ® (2)5(3), -90 ® -(2)(3)2(5)); 

2.    при обращении к facto с вложенным списком [[n]] возвращается фактор-множество n ([[96]] ® {2, 3}, [[-90]] ® {2, 3, 5});

3.    при обращении к facto со списком [n] возвращается список [s, [[p1, k1], [p2, k2], …, [pn, kn]]], где  – каноническое разложение n ([96] ® [1, [[2, 5], [3, 1]]], [-90] ® [-1, [[2, 1], [3, 2], [5, 1]]]).

        Решение. Приведенное ниже решение исходной задачи разбито на два этапа. Сначала создаются перегружаемые процедуры fact1, fact2 и fact3 решения отдельных задач 1-3. Затем по списку [fact1, fact2, fact3] с помощью команды overload создается перегружаемая процедура facto решения этих задач. При реализации процедур fact1, fact3 и fact2 использованы соответственно библиотечные процедуры ifactor и ifactors и процедура factorset пакета numtheory.

 

> 

restart:                                     # этап 1                                       

{¿}

> 

fact1 := proc(n::integer)

{Shift+¿}

 

   option overload;

{Shift+¿}

 

   if nargs = 1 then

{Shift+¿}

 

      ifactor(n)

{Shift+¿}

 

   else

{Shift+¿}

 

      print("Arguments are bad")

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

fact1(4680);

{¿}

> 

fact2 := proc(n::listlist(integer))

{Shift+¿}

 

   option overload;

{Shift+¿}

 

   if nargs = 1 and nops(n[1]) = 1 then

{Shift+¿}

 

      numtheory[factorset](n[1, 1])

{Shift+¿}

 

   else

{Shift+¿}

 

      print("Arguments are bad")

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

fact2([[4680]]);

{¿}

> 

fact3 := proc(n::list(integer))

{Shift+¿}

 

   option overload;

{Shift+¿}

 

   if nargs = 1 and nops(n) = 1 then

{Shift+¿}

 

      ifactors(n[1])

{Shift+¿}

 

   else

{Shift+¿}

 

      print("Arguments are bad")

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

fact3([4680]);

{¿}

> 

facto := overload([fact1, fact2, fact3]):  # этап 2

{¿}

> 

facto(8064);

{¿}

> 

facto([[8064]]);

{¿}

> 

facto([8064]);

{¿}

9.9.  Отладка программ

 

        Пусть имеется некоторая массовая проблема M, разработан или выбран алгоритм нахождения решения M и, в соответствии с ним, на конкретном языке программирования написана программа P. В данном случае под программой мы понимаем серию взаимосвязанных, то есть обращающихся друг к другу, процедур pr1, pr2, …, prN. Если при решении отдельных задач из M мы обращаемся к процедуре pr1, то ее будем называть главной или, по-другому, головной программой. Другие процедуры, используемые для решения задач из P, будем называть подпрограммами.   

        Под отладкой программ понимается следующее. Пусть подготовлены наборы входных данных, и пришла пора запустить созданную программу P на выполнение. Все что мы делаем с программой с этого момента времени и до получения ее правильно работающей версии на этих наборах данных и называется отладкой. Этап отладки обычно плавно переходит в этап тестирования, отличающийся от отладки лишь большим количеством решаемых по программе задач и их более четкой ориентированностью на реальные входные данные. На этапе отладки из программы “высыпаются” практически все синтаксические ошибки и большая часть простых логических ошибок. Тестирование ориентировано на проверку правильности взаимодействия отдельных частей программы и функционирования ее в целом. Этап тестирования позволяет обнаружить более тонкие и глубоко спрятанные в программе логические ошибки. Иногда этот этап завершается отказом от выбранного алгоритма решения задачи в силу его сложности, громоздкости или даже обнаруженной в нем ошибки. В любом случае нелишне помнить, что отладка и тестирование могут помочь в устранении большей части ошибок в программе, но практически никогда не могут гарантировать их отсутствия. Непосредственно к отладке и тестированию примыкает тема профилирования файлов – получения частотных и временных характеристик выполнения их отдельных фрагментов на конкретных наборах входных данных.

        Для помощи пользователю в отладке программ Maple предоставляет ряд специальных средств, а также универсальный графический редактор.

 

9.13.1.  Трассирование программ

       

        Итак, программа P решения проблемы M написана, и необходимо приступить к ее отладке. При обращениях к P с различными наборами входных данных, как правило, вместо ожидаемого ответа мы получаем те или иные системные сообщения о синтаксических  ошибках. Устранение этих ошибок из текста P особого труда не представляет. Но могут встретиться в P и логические ошибки. Они возникают по двум причинам: для решения M создан или выбран неправильный алгоритм, или алгоритм правильный, но его реализация P содержит ошибки. Но эти две глобальные причины порождаются огромным количеством возможным мелких и крупных просчетов в рассуждениях, описать и классифицировать которые невозможно. Тем не менее, найти логические ошибки в P необходимо и часто помочь в этом может простое трассирование программы на различных наборах входных данных. Под этим понимается процесс отслеживания и анализа результатов выполнения каждого предложения P. Возможность трассирования P обеспечивается двумя способами. Или пошаговым выполнением предложений P с оперативным выводом результатов вычислений на каждом шаге, или автоматическим выполнением P, но с получением листинга тех же данных, что и в первом случае. Трассирование в Maple реализуется или с помощью опции trace в процедурах, о чем мы уже говорили, или командой trace. Об этой команде и некоторых других средствах, помогающих отладке программ, и пойдет речь ниже.

Трассирование процедуры

        Функция trace, наряду с опцией trace, является инструментом отладки процедур. С процедурами, имеющими специальные вычислительные правила trace не работает (eval, seq, …). По trace можно отлаживать не только процедуры, но и модули. Альтернативными именами для trace и untrace являются соответственно имена debug и undebug.

        Функция trace(pr1) включает режим трассирования вычислений по процедуре pr1. Это означает, что после запуска команды при обращениях к pr1 в виде pr1(…); выводится следующая информация: входные параметры pr1, результат выполнения каждого предложения pr1 и возвращаемые pr1 данные. При обращении к pr1 в виде pr1(…): выводятся только входные параметры pr1 и возвращаемые pr1 данные.

        Функция untrace(pr1) отменяет трассирование процедуры pr1.

        Функция trace(pr1, pr2, …) включает режим трассирования процедур pr1, pr2, … Ее использование оправдано, если процедуры указанного списка составляют одну программу.

        Функция untrace(pr1, pr2, …) отменяет трассирование процедур pr1, pr2, …

        Пример 8. Здесь демонстрируется трассирование процедур. 

  

> 

restart:

{¿}

> 

ff := proc(x, u)

{Shift+¿}

 

   local y, b;

{Shift+¿}

 

   y := x+1; b:= x+y+u; 2*gg(b)+1

{Shift+¿}

 

end proc:

{¿}

> 

gg := proc(x)

{Shift+¿}

 

   local z;

{Shift+¿}

 

   z := x^2; z+7

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

trace(ff, gg);

{¿}

> 

ff(2, 1);

{¿}

Вывод семантической информации о процедуре

        Команда maplemint(pro) генерирует и выводит семантические ошибки процедуры pro и ее код, который ни при каких обращениях к pro выполняться не будет. Анализ такой информации может оказать существенную помощь в отладке процедуры. Если ошибки не найдены, то возвращается NULL выражение. Естественно, что по maplemint(pro)  отлавливаются не все семантические ошибки. С модулями функция пока не работает.

        Примеры 9. Здесь демонстрируется генерирование и вывод семантической информации о процедуре dot.

   >

restart:

{¿}

> 

dot := proc(x)

{Shift+¿}

 

   local b, g; global c;

{Shift+¿}

 

   if b < 9 then

{Shift+¿}

 

      b := 11+d; g := 99;

{Shift+¿}

 

      break;

{Shift+¿}

 

      if b < 9 then return("exit") end if;

{Shift+¿}

 

      if b > 3 then b^2 else b^3 end if

{Shift+¿}

 

   end if;

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

maplemint(dot);

{¿}

Режим вычислений с историей

        Функция .  history() включает режим history ввода-вывода данных с запоминанием истории вычислений в глобальных переменных O1, O2, … После запуска происходит смена стандартного знака приглашения “>” на составной знак “O1:=”. Выполнение (одного!) предложения, введенного после знака “O1:=”, приводит к выводу результатов вычислений и запоминанию их в переменной O1. Кроме того, появляется приглашение “O2:=” для ввода следующего предложения и т. д. В режиме history нельзя выполнить команду restart.

        Напомним, что часть истории вычислений может быть доступна в стандартном режиме с помощью дитто-операторов %, %%, %%%.

        Команда off переключает ввод с режима запоминания истории вычислений на стандартный режим с приглашением в виде знака “>”.

        Функция timing(expr) работает только в режиме history и возвращает время, затраченное на вычисление выражения expr.

        Команда clear работает только в режиме history, удаляя значения созданных переменных O1, O2, … Режим вычислений history остается, но появляется начальный знак приглашения “O1:=”.

        Примеры 10. Здесь демонстрируется работа в документе в режиме с запоминанием истории вычислений. 

   >

restart:

{¿}

> 

history()

{Shift+¿}

O1:=

a+b*c;

{¿}

O2:=

sin(x)+O1;

{¿}

O3:=

clear;

{¿}

O1:=

9+8+as;

{¿}

O2:=

off;

{¿}

> 

 

9.13.2.  Графический отладчик программ

        Система Maple имеет простой и достаточно удобный интерактивный графический отладчик “Maple Debugger”. Он открывается при обращении к программе по первой контрольной точке, установленной в ней функциями stopat, stopwhen или stoperror, или щелчком мыши по кнопке Debug the current operation панели инструментов (пиктограмма ). Программы отлаживаются обращениями к ним с разными наборами фактических параметров.

        Начнем разговор о графическом отладчике с описания функций stopat, stopwhen или stoperror.                

 

Установка контрольных точек на предложения процедуры и чистка их

        Когда выполнение процедуры доходит до предложения с любой контрольной точкой, вызывается отладчик, если он еще не вызван, и вычисления приостанавливаются. Если эта точка установлена по stopat, то в окне вывода отладчика индицируется следующая информация: результат вычислений последнего выполненного предложения, “имя процедуры:”, номер предложения с контрольной меткой и само это предложение. Далее можно начинать отладку, вводя команды в поле ввода комбинированного списка, или выполняя команды общего назначения с помощью соответствующих кнопок отладчика. Например, щелчок левой кнопкой мыши по кнопке Continue форсирует выполнение программы до встречи со следующей ближайшей контрольной точкой любого типа.

        Функция stopat() возвращает список имен всех процедур, у которых имеются контрольные точки на каких-либо предложениях. Для просмотра листинга процедуры с пронумерованными предложениями можно использовать функцию showstat(pro).

        Функция unstopat() удаляет все контрольные точки, установленные по stopat во всех процедурах.

        Функция stopat(pro) устанавливает контрольную точку в процедуре pro на ее первом предложении. В этом случае вызов процедуры открывает отладчик, в котором можно проследить выполнение pro с самого начала.

        Функция unstopat(pro) удаляет все контрольные точки, установленные по stopat в процедуре pro.

        Функция stopat(pro, num) устанавливает контрольную точку в процедуре pro на ее предложении с номером num.

        Функция unstopat(pro, num) удаляет контрольную точку, установленную по stopat в процедуре pro на ее предложении с номером num.

        Функция stopat(pro, num, bo) устанавливает условную контрольную точку в процедуре pro на ее предложении с номером num. Прерывание в данной точке действительно будет происходить лишь в том случае, когда булево выражение bo имеет значение, равное true. В bo можно использовать операторы  <, <=, >=, >, =, <>, and, or, или not, фактические параметры, а также локальные и глобальные переменные процедуры.

        Функция showstat(pro) выводит листинг пользовательской или библиотечной процедуры pro. При этом в листинг не включаются комментарии и разделы option и description, а предложения тела pro пронумерованы натуральными числами от единицы и далее. Номера предложений с безусловными контрольными точками метятся справа символами “*”. Номера предложений с условными контрольными точками метятся справа символами “?”. Номер текущего предложения pro в отладчике отмечается символом “!”. Встроенные процедуры по G не выводятся.

        Функция showstat(). Здесь выводятся листинги всех процедур, которые имеют контрольные точки на предложениях.

        Функция DEBUG(…). Если в некоторые позиции текста процедуры pro поместить функцию debug, то для следующих за ними предложений будут установлены контрольные точки. Они отличаются от точек, определяемых по stopat только тем, что их нельзя удалить командой unstopat. Функция через один или более своих аргументов может передавать отладчику какие-либо параметры. Их значение появляется в поле вывода отладчика. Если никакие параметры по debug не переданы, то в поле вывода появляется результат последних вычислений по pro. Из отлаженной процедуры функции debug необходимо удалить.   

        Примеры 11. Здесь на иллюстративном примере демонстрируется установка по stopat контрольных точек в процедурах spy и gg, а также выход из spy в отладчик. После инициализации глобальной переменной a и ввода процедур spy и gg контрольные точки формируются так. Первая точка устанавливается по stopat(spy, 3, x < y) на третьем предложении spy. Она условная в том смысле, что прерывания по ней будут происходить только тогда, когда при обращении к spy первый аргумент меньше второго аргумента. Вторая точка устанавливается по stopat(spy, 5) на пятом предложении spy. И, наконец, третья точка устанавливается по stopat(gg, 2) на втором предложении gg. Выведенный список [gg, spy] показывает, какие процедуры имеют контрольные точки. Обращение spy(1, 2) выводит нас в отладчик, в котором приостанов вычислений происходит на первой контрольной точке spy, то есть на предложении s := x-a. Щелчок левой кнопкой мыши по кнопке Showstat приводит к выводу в отладчике листинга процедур spy и gg с пронумерованными предложениями и контрольными точками, помеченными символами “*” и “?”. Знаком “!” помечается текущее предложение. Для реальной работы в отладчике необходимо знать его команды, реализуемым кнопками, и команды, вводимые в поле ввода комбинированного списка..

> 

restart:

{Shift+¿}

 

a := 10:

{¿}

> 

spy := proc(x::posint, y::posint)

{Shift+¿}

 

   description "illustrative program";

{Shift+¿}

 

   local j, s, t;

{Shift+¿}

 

   global a;

{Shift+¿}

 

   a := a+1;

{Shift+¿}

 

   for j from 1 to 4 do

{Shift+¿}

 

       s := x-j+a; t := s+y; t-gg(j+x+y)

{Shift+¿}

 

   end do

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

gg := proc(z::posint)

{Shift+¿}

 

   local a, s; # second procedure

{Shift+¿}

 

   a := 0; s := 100; z+a+s;

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

stopat(spy, 3, x < y); stopat(spy, 5); stopat(gg, 2);

{¿}

> 

spy(1, 2);          # далее находимся в отладчике

{¿}

 

11

 

 

spy:

 

 

  3?  s := x-a

 

 

==================================

 

 

{Showstat}       # “нажимаем кнопку Showstat

 

 

gg := proc(z::posint)

 

 

local a, s;

 

 

   1    a := 0;

 

 

   2*  s := 100;

 

 

   3    z+a+s

 

 

end proc

 

 

 

 

 

spy := proc(x::posint, y::posint)

 

 

local j, s, t;

 

 

global a;

 

 

   1   a := a+1

 

 

   2   for j to 4 do

 

 

   3?!   s := x-a;

 

 

   4      t := s+y;

 

 

   5*    t-gg(x+y)

 

 

       end do

 

 

end proc

 

 

 

 

 

spy:

 

 

  2?  s:= x-a

 

 

===================================

 

 

{Quit}                       # “нажимаем кнопку Quit

 

> 

unstopat():        # далее находимся в документе

{¿}

 

[]

Установка и чистка контрольных точек для переменных процедуры

        Когда выполнение процедуры доходит до предложения с любой контрольной точкой, вызывается отладчик, если он еще не вызван, и вычисления приостанавливаются. Если эта точка установлена по stopwhen, то в окне вывода отладчика индицируется следующая информация: выражение вида “var := текущее значение”, “имя процедуры:”, номер предложения с контрольной точкой для var и следующее выполняемое предложение. Далее можно начинать отладку, вводя команды в поле ввода комбинированного списка, или, выполняя команды общего назначения с помощью соответствующих кнопок отладчика. Например, щелчок левой кнопкой мыши по кнопке Continue форсирует выполнение программы до встречи со следующей ближайшей контрольной точкой любого типа.

        Функция stopwhen() возвращает список всех имеющихся контрольных точек переменных для всех процедур. При этом каждый элемент этого списка есть имя переменной, если она глобальная, или список из имени процедуры и имени переменной, если последняя локальная.

        Функция unstopwhen() вычищает контрольные точки всех переменных.

        Функция stopwhen(var) устанавливает контрольные точки для глобальной переменной var.

        Функция unstopwhen(var) вычищает контрольные точки глобальной переменной var.

        Функция stopwhen([pro, var]) устанавливает контрольные точки для локальной переменной var процедуры pro.

        Функция unstopwhen([pro, var]) вычищает все контрольные точки локальной переменной var процедуры pro.

        Примеры 12. Здесь на процедурах spy и gg предыдущего примера демонстрируется установка по stopwhen контрольных точек и выход из spy в отладчик. Первая точка устанавливается в spy по stopwhen([spy, s]) для локальной переменной s. Вторая точка устанавливается по stopwhen(a) для глобальной переменной a. Третья точка устанавливается в gg по stopwhen([gg, a]) для локальной переменной a. И, наконец, четвертая точка устанавливается по stopat(gg, 2) на втором предложении gg. Выведенные списки [a, [spy, s], [gg, a]] и [gg] показывает, какие процедуры имеют контрольные точки и каковы они. Обращение spy(1, 5) выводит нас в отладчик. Поскольку средства отладчика еще не освоены, пока можно перемещаться в программе по установленным в ней контрольным точкам. Для этого достаточно щелкать левой кнопкой мыши по кнопке Continue.

 

> 

restart:

{Shift+¿}

 

a := 10:

{¿}

> 

spy := proc(x::posint, y::posint) …    # из примера 11

{¿}

> 

gg := proc(z::posint) …                      # из примера 11

{¿}

 

 

 

> 

stopwhen([spy, s]): stopwhen(a): stopwhen([gg, a]);

{Shift+¿}

 

stopat(gg, 2);

{¿}

> 

spy(1, 5);             # далее находимся в отладчике

{¿}

 

a := 11

 

 

spy:

 

 

   2   for j to 4 do

 

 

       

 

 

       end do

 

 

======================================

 

 

{Continue}              # “нажимаем кнопку Showstat

 

 

s := 11

 

 

spy:

 

 

   4     t := s+y;

 

 

======================================

 

 

{Continue} … # продолжайтенажиматьContinue

 

 

======================================

 

 

***Debugging Complete***

 

 

{Quit}                              # “нажимаем кнопку Quit

 

> 

unstopwhen()          # далее находимся в документе

 

 

[]

Установка и чистка контрольных точек для ошибок

        Когда выполнение процедуры доходит до предложения с любой контрольной точкой, вызывается отладчик, если он еще не вызван, и вычисления приостанавливаются. Если эта точка установлена по stoperror, то в окне вывода отладчика индицируется следующая информация: сообщение о типе произошедшей ошибки, “имя процедуры:”, номер предложения с ошибкой и само это предложение. Далее можно начинать отладку, вводя команды в поле ввода комбинированного списка, или, выполняя команды общего назначения с помощью соответствующих кнопок отладчика.

        Функция stoperror() возвращает список всех ошибок с установленными контрольными точками. В этом списке параметр all интерпретируется как любые неотлавливаемые ошибки, а параметр traperror – как любые отлавливаемые ошибки.

       Функция unstoperror() удаляет контрольные точки для любых ошибок всех процедур.

        Функция stoperror(all) устанавливает динамичные контрольные точки для процедур в случае возникновения в них ошибок при вычислениях. После выполнения C любая появившаяся ошибка, если она программно не отловлена каким-либо способом, например, по try, будет приводить не к прерыванию вычислений, а к вызову отладчика.

        Функция unstoperror(all) удаляет контрольные точки для любых отлавливаемых ошибок всех процедур.

        Функция stoperror(traperror) здесь отладчик вызывается не по неотловленным ошибкам, а, наоборот, по ошибкам, которые удалось отловить.

        Функция unstoperror(traperror) удаляет контрольные точки для любых отлавливаемых оши­бок всех процедур.

        Функция stoperror(msg) здесь вызов отладчика будет происходить только при возникновении конкретной ошибки, задаваемой аргументом msg, например, `numeric exception: division by zero` (деление на нуль).

        Функция unstoperror(msg) удаляет контрольные точки для ошибки, задаваемой аргументом msg.

        Замечание. Для некоторых из ошибок контрольные точки по stoperror установить нельзя. К ним относятся ошибки: out of memory (не хватает памяти), too many levels of recursion (слишком много уровней рекурсии) и object too large (объект слишком большой). Далее, следует иметь в виду, что нами описаны далеко не все возможности функции stoperror.

 

        Графический отладчик. Теперь пора перейти к описанию графического отладчика и приемов работы с ним. Общий вид отладчика представлен на рисунке 1. В нем имеются следующие объекты:

·       неименованное окно вывода и просмотра отладочной информации, расположенное сразу за строкой заголовка;

·       комбинированный список “Enter a debugger command” для ввода команд отладки или выбора их из списка ранее выполненных команд. Этот список расположен ниже окна просмотра;

·       кнопка “Execute” для запуска на выполнение команды, находящейся в первой строке комбинированного списка. Кнопка расположена справа от списка. Щелчок левой кнопкой мыши по этой кнопке равносилен нажатию ключа {¿};

·       группа “Common Debugger Comands” из 12 независимых кнопок, щелчки левой кнопкой мыши по которым реализуют те или иные команды общего назначения. Эти кнопки расположены ниже комбинированного списка. Кнопочные, а также многие другие команды, можно выполнять и через комбинированный список;

·       независимый переключатель “Copy Results on Exit” для включения (n) или выключения ( ) режима копирования отладочной информации из окна просмотра в документ при выходе из режима отладки. Переключатель расположен с левой стороны в нижней части отладчика.

 

Рис. 1.  Общий вид интерактивного графического отладчика процедур

        Дадим краткое пояснение командам общего назначения, реализуемым кнопками отладчика. Команда Quit закрывает отладчик, передавая управление документу. Выполнение команд Next, Step, In To, Out From, Continue завершается при встрече с любой контрольной точкой, и управление передается отладчику. Во всех других случаях нормальное выполнение команд также завершается передачей управления отладчику. Из всех кнопочных команд наиболее часто приходится использовать команды Continue – перемещение по контрольным точкам, и Step – перемещение по предложениям. Итак, назначение команд следующее:

·       Next – перейти к выполнению следующего предложения на текущем уровне. Структуры for, if , … , в том числе и вложенные, выполняются как одно предложение и входы в них не производятся. Не происходят входы и внутрь процедур при обращении к ним;

·       Step – перейти к выполнению следующего предложения вне зависимости от уровня его расположения. При этом реализуются входы внутрь управляющих структур, в том числе и вложенных, и внутрь процедур по обращениям к ним и. т. д.;

·       In To – перейти к выполнению следующего предложения. Команда
In to по действию является промежуточной между командами Next и Step. По ней реализуются входы внутрь управляющих структур, в том числе и вложенных, но не внутри вызванных процедур;

·       Out From – выйти из процедуры, цикла или структуры if на один уровень вверх;

·       Continue – продолжить выполнение до встречи со следующей контрольной точкой;

·       List – вывести листинг, возможно неполный, текущей процедуры;

·       Showstat – индицировать в окне вывода листинг всех процедур, имеющих контрольные точки, установленные по stopat. Формат листинга тот же, что и при выводе функцией showstat().

·       Help – открыть страницу помощи по отладчику (аналог команды ?debugger);

·       Clear – удалить содержимое окна вывода;

·       Return – выйти из текущей процедуры;

·       Quit – прервать вычисления и выйти из отладчика;

·       Copy – копировать отладочный контент в документ.

 

        Кратко опишем теперь команды отладки процедур, которые мы можем выполнять из редактора с помощью комбинированного списка Enter a debugger command”. Таких списочных команд намного больше, чем ранее рассмотренных кнопочных команд. Их можно или вводить в первую строку списка, или выбирать из списка. Вот эти команды (proимя процедуры):

·       next – выполняется та же команда, что и по кнопке Next;

·       step – выполняется та же команда, что и по кнопке Step;

·       into – выполняется та же команда, что и по кнопке In To;

·       outfrom – выполняется та же команда, что и по кнопке Out From;

·       cont – выполняется та же команда, что и по кнопке Continue;

·       return – выполняется та же команда, что и по кнопке Return;

·       quit, done, stop – выполняется та же команда, что и по кнопке Quit;

·       where lev. Здесь необязательный параметр lev – натуральное число. Он задает уровень вывода вызовов процедур, в которых происходил приостанов вычислений. При отсутствии lev выводится содержимое всего стека вызовов. При наличии lev выводится содержимое стека вызовов с внешнего уровня lev до уровня 1 с текущей процедурой;

·       showstack – выводит список имен вызываемых процедур от текущего до верхнего уровня;

·       showstat a. Здесь a Î {Æ, pro, pro n1, pro n1..n2}. При отсутствии a выводится листинг текущей процедуры. Его формат тот же, что и при выводе функцией showstat(). При a = pro выводится полный листинг процедуры pro. При a = pro n1 выводится листинг процедуры pro, в котором пропущены все строки, кроме строки с номером n1. При a = pro n1..n2 выводится листинг процедуры pro, в котором пропущены все строки, кроме строк из диапазона номеров n1..n2. Отсутствующие строки листинга заменяются символом “…”;

·       list a. Здесь a Î {Æ, pro, pro n1, pro n1..n2}. Данная команда выполняется аналогично showstat за тем исключением, что при отсутствии a выводится пять предыдущих предложений, текущее предложение и следующее за ним предложение процедуры pro.

·       showstop – выводит имена процедур имеющих контрольные точки, установленные по stopat, stopwhen и stoperror;

·       stopat b. Здесь b Î {Æ, pro, pro n, pro n cond}. Эта команда действует аналогично функции stopat(b), устанавливая контрольные точки на предложения процедуры pro. Правда, здесь при установке безусловных контрольных точек в текущей процедуре можно не указывать ее имя pro;

·       unstopat b. Здесь b Î {Æ, pro, pro n}. Эта команда действует аналогично функции unstopat(b);

·       stopwhen d. Здесь d Î {Æ, var, pro var}. Эта команда действует аналогично функции stopwhen(d), устанавливая и возвращая контрольные точки на локальные и глобальные переменные;

·       unstopwhen. Здесь d Î {var, pro var}. Эта команда действует аналогично функции unstopwhen(d);

·       stoperror e. Здесь e Î {Æ, all, traperror, msg}. Эта команда действует аналогично функции stoperror(e);

·       unstoperror e. Здесь e Î {Æ, all, traperror, msg}. Эта команда действует аналогично функции unstoperror(e);

·       showerror, showexception. По эти командам выводятся соответственно список с сообщением об ошибке и список из имени процедуры и сообщения об ошибке (аналоги lasterror и lastecxeption);

·       expr. Здесь expr – любое Maple-выражение. Оно вычисляется в контексте текущей процедуры, то есть так же, как если бы expr находилось внутри этой процедуры. Результат вычислений индицируется в отладчике;